﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Globalization;
using System.Xml.Linq;
using System.IO;
using System.Xml;
using System.ComponentModel.Composition;
using System.Security.Permissions;
using System.Reflection;
using System.Threading;
namespace SQLCE.Helpers
{
    /// <summary>
    /// 本地化（国际化）
    /// </summary>
    public static class LocalizeExtension
    {
        public static string Localize(this string source, CultureInfo culture)
        {
            return source.Localize(null, culture);
        }
        public static string Localize(this string source, string category = "")
        {
            return source.Localize(category, System.Threading.Thread.CurrentThread.CurrentUICulture);
        }
        public static string Localize(this string source, string category, CultureInfo culture)
        {
            return source.Map(source, category, culture).Value;
        }
        public static Element Map(this string source, string key, string category = "")
        {
            return source.Map(key, category, System.Threading.Thread.CurrentThread.CurrentUICulture);
        }
        public static Element Map(this string source, string key, string category, CultureInfo culture)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                return Element.Empty;
            }
            var repository = ElementRepository.DefaultRepository;
            var element = repository.Get(key, category, culture.Name);
            if (element == null)
            {
                element = new Element() { Name = source, Category = category, Culture = culture.Name, Value = source };
                repository.Add(element);
            }
            return element;
        }
        public static string Localize(this Element element)
        {
            return element.Value
            .Map(element.Name, element.Category, CultureInfo.GetCultureInfo(element.Culture))
            .Value;
        }
        public static string Position(this string str, string position)
        {
            return String.Format("<span title=\"{1}\">{0}</span>", str, position);
        }
    }
    public class Element
    {
        public string Name
        {
            get;
            set;
        }
        public string Value
        {
            get;
            set;
        }
        public string Culture
        {
            get;
            set;
        }
        public virtual CultureInfo GetCultureInfo()
        {
            return System.Globalization.CultureInfo.GetCultureInfo(this.Culture);
        }
        public string Category
        {
            get;
            set;
        }
        static Element _Empty;
        public static Element Empty
        {
            get
            {
                if (_Empty == null)
                {
                    _Empty = new Element
                    {
                        Name = "",
                        Value = "",
                        Category = "",
                        Culture = ""
                    };
                }
                return _Empty;
            }
        }
    }
    public static class ElementRepository
    {
        public static IElementRepository DefaultRepository = new CacheElementRepository(new XmlElementRepository());
    }
    public class ElementCategory
    {
        public string Category { get; set; }
        public string Culture { get; set; }
    }
    public interface IElementRepository
    {
        IQueryable<CultureInfo> EnabledLanguages();
        IQueryable<Element> Elements();
        Element Get(string name, string category, string culture);
        IQueryable<ElementCategory> Categories();
        bool Add(Element element);
        bool Update(Element element);
        bool Remove(Element element);
        void Clear();
        void AddCategory(string category, string culture);
        bool RemoveCategory(string category, string culture);
    }
    public class ElementCacheKeyEqualityComparer : IEqualityComparer<ElementCacheKey>
    {
        #region IEqualityComparer<ElementCacheKey> Members
        public bool Equals(ElementCacheKey x, ElementCacheKey y)
        {
            return x.Equals(y);
        }
        public int GetHashCode(ElementCacheKey obj)
        {
            ElementCacheKey key = (ElementCacheKey)obj;
            return key.Hash;
        }
        #endregion
    }
    public class ElementCacheKey
    {
        public ElementCacheKey(Element element)
            : this(element.Name, element.Category, element.Culture)
        {
        }
        public ElementCacheKey(string name, string category, string culture)
        {
            if (string.IsNullOrEmpty(name))
                throw new ArgumentNullException("name");
            this.Name = name ?? "";
            this.Category = category ?? "";
            this.Culture = culture ?? "";
            this.Hash = (("Name:" + this.Name.ToLower()) + (";category:" + this.Category.ToLower()) + (";culture:" + culture.ToLower())).GetHashCode();
        }
        public string Name { get; private set; }
        public string Category { get; private set; }
        public string Culture { get; private set; }
        public int Hash { get; private set; }
        public override int GetHashCode()
        {
            return Hash;
        }
        public override bool Equals(object obj)
        {
            return this.Hash == ((ElementCacheKey)obj).GetHashCode();
        }
    }
    public class CacheElementRepository : IElementRepository
    {
        System.Collections.Hashtable CachedElements = new System.Collections.Hashtable();
        private IElementRepository inner;
        public CacheElementRepository(IElementRepository innerRepository)
        {
            inner = innerRepository;
        }
        public IQueryable<System.Globalization.CultureInfo> EnabledLanguages()
        {
            return inner.EnabledLanguages();
        }
        public IQueryable<Element> Elements()
        {
            return inner.Elements();
        }
        public Element Get(string name, string category, string culture)
        {
            var cachedKey = new ElementCacheKey(name, category, culture);
            Element element = CachedElements[cachedKey] as Element;
            //.FirstOrDefault(i => i.Name == key && i.Category == category && i.Culture == culture.Name);
            if (element == null)
            {
                element = inner.Get(name, category, culture);
                if (element != null)
                {
                    AddCache(element);
                }
            }
            return element;
        }
        public void AddCache(Element element)
        {
            var cacheKey = new ElementCacheKey(element);
            CachedElements[cacheKey] = element;
        }
        public void RemoveCache(Element element)
        {
            var cacheKey = new ElementCacheKey(element);
            lock (CachedElements)
            {
                if (CachedElements.ContainsKey(cacheKey))
                {
                    CachedElements.Remove(cacheKey);
                }
            }
        }
        public void ClearCache()
        {
            lock (CachedElements)
            {
                CachedElements.Clear();
            }
        }
        public IQueryable<ElementCategory> Categories()
        {
            return inner.Categories();
        }
        public bool Add(Element element)
        {
            var r = inner.Add(element);
            if (r)
            {
                AddCache(element);
            }
            return r;
        }
        public bool Update(Element element)
        {
            var r = inner.Update(element);
            if (r)
            {
                RemoveCache(element);
            }
            return r;
        }
        public bool Remove(Element element)
        {
            var r = inner.Remove(element);
            if (r)
            {
                RemoveCache(element);
            }
            return r;
        }
        public void AddCategory(string category, string culture)
        {
            inner.AddCategory(category, culture);
        }
        public bool RemoveCategory(string category, string culture)
        {
            var r = inner.RemoveCategory(category, culture);
            ClearCache();
            return r;
        }
        public void Clear()
        {
            inner.Clear();
            CachedElements.Clear();
        }
    }
    public class MemoryElementRepository : IElementRepository
    {
        static Dictionary<string, Element> CachedElements
        {
            get;
            set;
        }
        public MemoryElementRepository(IElementRepository elementRepository)
        {
            if (elementRepository != null)
            {
                this.InnerElementRepository = elementRepository;
                //this.InnerElementRepository.Added += new Action<Element>(InnerElementRepository_Added);
                //this.InnerElementRepository.Removed += new Action<Element>(InnerElementRepository_Removed);
                //this.InnerElementRepository.Updated += new Action<Element>(InnerElementRepository_Updated);
            }
            CachedElements = new Dictionary<string, Element>();
        }
        void InnerElementRepository_Updated(Element obj)
        {
            lock (CachedElements)
            {
                CachedElements[GetElementDictionaryKey(obj)] = obj;
            }
        }
        void InnerElementRepository_Removed(Element obj)
        {
            lock (CachedElements)
            {
                CachedElements.Remove(GetElementDictionaryKey(obj));
            }
        }
        void InnerElementRepository_Added(Element obj)
        {
            lock (CachedElements)
            {
                CachedElements[GetElementDictionaryKey(obj)] = obj;
            }
        }
        IElementRepository InnerElementRepository
        {
            get;
            set;
        }
        public IQueryable<System.Globalization.CultureInfo> EnabledLanguages()
        {
            if (this.InnerElementRepository != null)
            {
                return this.InnerElementRepository.EnabledLanguages();
            }
            return Enumerable.Empty<System.Globalization.CultureInfo>().AsQueryable();
        }
        public IQueryable<Element> Elements()
        {
            if (this.InnerElementRepository != null)
            {
                return this.InnerElementRepository.Elements();
            }
            return CachedElements.Values.AsQueryable();
        }
        public Element Get(string name, string category, string culture)
        {
            string dictionaryKey = GetElementDictionaryKey(name, category, culture);
            Element element = null;
            if (CachedElements.ContainsKey(dictionaryKey))
            {
                element = CachedElements[dictionaryKey];
            }
            if (element == null && this.InnerElementRepository != null)
            {
                element = this.InnerElementRepository.Get(name, category, culture);
                if (element != null)
                {
                    CachedElements.Add(dictionaryKey, element);
                }
            }
            return element;
        }
        public bool Add(Element element)
        {
            var result = false;
            if (this.InnerElementRepository != null)
            {
                result = this.InnerElementRepository.Add(element);
                if (result == true)
                {
                    this.OnAdded(element);
                }
            }
            else
            {
                InnerElementRepository_Added(element);
                this.OnAdded(element);
                return true;
            }
            return result;
        }
        public bool Update(Element element)
        {
            var result = false;
            if (this.InnerElementRepository != null)
            {
                this.InnerElementRepository.Update(element);
                if (result == true)
                {
                    this.OnUpdated(element);
                }
            }
            else
            {
                InnerElementRepository_Updated(element);
                this.OnAdded(element);
                return true;
            }
            return result;
        }
        public bool Remove(Element element)
        {
            var result = false;
            if (this.InnerElementRepository != null)
            {
                this.InnerElementRepository.Remove(element);
                if (result == true)
                {
                    this.OnRemoved(element);
                }
            }
            else
            {
                InnerElementRepository_Removed(element);
                this.OnRemoved(element);
                return true;
            }
            return result;
        }
        void OnAdded(Element element)
        {
            var added = this.Added;
            if (added != null)
            {
                added(element);
            }
        }
        void OnRemoved(Element element)
        {
            var removed = this.Removed;
            if (removed != null)
            {
                removed(element);
            }
        }
        void OnUpdated(Element element)
        {
            var updated = this.Updated;
            if (updated != null)
            {
                updated(element);
            }
        }
        string GetElementDictionaryKey(Element element)
        {
            return GetElementDictionaryKey(element.Name, element.Category, element.Culture);
        }
        string GetElementDictionaryKey(string name, string category, string culture)
        {
            return name + category + culture;
        }
        public event Action<Element> Added;
        public event Action<Element> Updated;
        public event Action<Element> Removed;

        #region IElementRepository 成员


        public IQueryable<ElementCategory> Categories()
        {
            throw new NotImplementedException();
        }

        public void Clear()
        {
            throw new NotImplementedException();
        }

        public void AddCategory(string category, string culture)
        {
            throw new NotImplementedException();
        }

        public bool RemoveCategory(string category, string culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
    [PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Shared)]
    [Export(typeof(XmlElementRepository))]
    public class XmlElementRepository : IElementRepository, IDisposable
    {
        private string path;
        ResXResource resx;
        public XmlElementRepository()
            : this(Path.Combine(Settings.BaseDirectory, "I18N"))
        {
        }
        public XmlElementRepository(string path)
        {
            this.path = path;
            if (Directory.Exists(path) == false)
            {
                try
                {
                    Directory.CreateDirectory(path);
                }
                catch (IOException exception)
                {
                    throw new DirectoryCreateException(exception);
                }
            }
            resx = new ResXResource(path);
        }
        public IQueryable<System.Globalization.CultureInfo> EnabledLanguages()
        {
            return Enumerable.Empty<System.Globalization.CultureInfo>().AsQueryable();
        }
        public IQueryable<Element> Elements()
        {
            return resx.GetElements();
        }
        public Element Get(string name, string category, string culture)
        {
            return resx.GetElement(name, category, culture);
        }
        public bool Add(Element element)
        {
            if (element == null)
            {
                return false;
            }
            if (resx.GetElement(element.Name, element.Category, element.Culture) == null)
            {
                resx.AddResource(element);
                return true;
            }
            else
            {
                return false;
            }
        }
        public bool Update(Element element)
        {
            if (element == null)
            {
                return false;
            }
            return resx.UpdateResource(element);
        }
        public bool Remove(Element element)
        {
            if (element == null)
            {
                return false;
            }
            return resx.RemoveResource(element);
        }
        #region IElementRepository Members
        public IQueryable<ElementCategory> Categories()
        {
            return resx.GetCategories().AsQueryable();
        }
        #endregion
        #region IDisposable Members
        public void Dispose()
        {
        }
        #endregion
        public bool RemoveCategory(string category, string culture)
        {
            return resx.RemoveCategory(category, culture);
        }
        public void AddCategory(string category, string culture)
        {
            resx.AddCategory(category, culture);
        }
        public void Clear()
        {
            if (File.Exists(path))
            {
                File.Delete(path);
            }
        }
    }
    public static class ResXResourceFileHelper
    {
        public static void Parse(string fileName, out string culture, out string category)
        {
            var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(Path.GetFileName(fileName));
            if (!fileNameWithoutExtension.Contains('.'))
            {
                culture = fileNameWithoutExtension;
                category = string.Empty;
            }
            else
            {
                var pointIndex = fileNameWithoutExtension.LastIndexOf('.');
                culture = fileNameWithoutExtension.Substring(pointIndex + 1);
                category = fileNameWithoutExtension.Substring(0, pointIndex);
            }
        }
        public static string GetFileName(string category, string culture)
        {
            if (string.IsNullOrWhiteSpace(category))
            {
                return culture + ".resx";
            }
            else
            {
                return category + "." + culture + ".resx";
            }
        }
    }
    public class DirectoryCreateException : Exception
    {
        public DirectoryCreateException()
            : base("No permission to create a directory.")
        {
        }
        public DirectoryCreateException(System.IO.IOException exception)
            : base("No permission to create a directory.", exception)
        {
        }
    }
    public class ResXResource
    {
        private static System.Threading.ReaderWriterLockSlim locker = new ReaderWriterLockSlim();
        private string path;
        public ResXResource(string path)
        {
            this.path = path;
            if (Directory.Exists(this.path) == false)
            {
                try
                {
                    Directory.CreateDirectory(path);
                }
                catch (IOException exception)
                {
                    throw new DirectoryCreateException(exception);
                }
            }
        }
        public bool RemoveCategory(string category, string culture)
        {
            locker.EnterWriteLock();
            try
            {
                string[] files = Directory.GetFiles(path);
                foreach (var file in files)
                {
                    string culture1;
                    string category1;
                    ResXResourceFileHelper.Parse(file, out culture1, out category1);
                    if (category.EqualsOrNullEmpty(category1, StringComparison.CurrentCultureIgnoreCase) && culture1.EqualsOrNullEmpty(culture, StringComparison.CurrentCultureIgnoreCase))
                    {
                        File.Delete(file);
                    }
                }
                return true;
            }
            finally
            {
                locker.ExitWriteLock();
            }
        }
        public void AddCategory(string category, string culture)
        {
            string filePath = Path.Combine(this.path, ResXResourceFileHelper.GetFileName(category, culture));
            XDocument document = GetResxDocument(filePath);
            if (document == null)
            {
                document = CreateResXDocument();
            }
            locker.EnterWriteLock();
            try
            {
                document.Save(filePath);
            }
            finally
            {
                locker.ExitWriteLock();
            }
        }
        public IEnumerable<ElementCategory> GetCategories()
        {
            string[] files = Directory.GetFiles(path);
            foreach (var file in files)
            {
                string culture;
                string category;
                ResXResourceFileHelper.Parse(file, out culture, out category);
                yield return new ElementCategory() { Category = category, Culture = culture };
            }
        }
        public IQueryable<Element> GetElements()
        {
            locker.EnterReadLock();
            try
            {
                string[] files = Directory.GetFiles(path);
                IEnumerable<Element> elements = Enumerable.Empty<Element>();
                for (int i = 0; i < files.Length; i++)
                {
                    string fileName = files[i];
                    if (fileName.EndsWith(".resx"))
                    {
                        XDocument doc = XDocument.Load(files[i]);
                        string culture;
                        string category;
                        ResXResourceFileHelper.Parse(fileName, out culture, out category);
                        IEnumerable<Element> newElements = doc.Root.Elements("data")
                        .Select(x => new Element()
                        {
                            Category = category,
                            Culture = culture,
                            Name = x.Attribute("name").Value,
                            Value = x.Element("value").Value
                        });
                        elements = elements.Union(newElements);
                    }
                }
                return elements.AsQueryable<Element>();
            }
            finally
            {
                locker.ExitReadLock();
            }
        }
        public Element GetElement(string name, string category, string culture)
        {
            locker.EnterReadLock();
            try
            {
                string filePath = Path.Combine(this.path, ResXResourceFileHelper.GetFileName(category, culture));
                XDocument document = GetResxDocument(filePath);
                if (document == null)
                {
                    return null;
                }
                return document.Root.Elements("data")
                .Where(it => it.Attribute("name").Value.EqualsOrNullEmpty(name, StringComparison.OrdinalIgnoreCase))
                .Select(it => new Element()
                {
                    Name = it.Attribute("name").Value,
                    Value = it.Element("value").Value,
                    Category = category,
                    Culture = culture
                }).FirstOrDefault();
            }
            finally
            {
                locker.ExitReadLock();
            }
        }
        public void AddResource(Element element)
        {
            locker.EnterWriteLock();
            try
            {
                string filePath = Path.Combine(this.path, ResXResourceFileHelper.GetFileName(element.Category, element.Culture));
                XDocument document = GetResxDocument(filePath);
                if (document == null)
                {
                    document = CreateResXDocument();
                }
                var exists = document.Root.Elements("data")
                .FirstOrDefault(d => d.Attribute("name").Value == element.Name);
                if (exists == null)
                {
                    document.Root.Add(
                    new XElement("data",
                    new XAttribute("name", element.Name),
                    new XAttribute(XNamespace.Xml + "space", "preserve"),
                    new XElement("value", element.Value)));
                    document.Save(filePath);
                }
            }
            finally
            {
                locker.ExitWriteLock();
            }
        }
        public bool UpdateResource(Element element)
        {
            locker.EnterWriteLock();
            try
            {
                string filePath = Path.Combine(this.path, ResXResourceFileHelper.GetFileName(element.Category, element.Culture));
                XDocument document = GetResxDocument(filePath);
                if (document == null)
                {
                    return false;
                }
                var newElement = document.Root.Elements("data")
                .FirstOrDefault(d => d.Attribute("name").Value.EqualsOrNullEmpty(element.Name, StringComparison.OrdinalIgnoreCase));
                if (newElement == null)
                {
                    return false;
                }
                newElement.Element("value").Value = element.Value;
                document.Save(filePath);
                return true;
            }
            finally
            {
                locker.ExitWriteLock();
            }
        }
        public bool RemoveResource(Element element)
        {
            locker.EnterWriteLock();
            try
            {
                string filePath = Path.Combine(this.path, ResXResourceFileHelper.GetFileName(element.Category, element.Culture));
                XDocument document = GetResxDocument(filePath);
                if (document == null)
                {
                    return false;
                }
                var newElement = document.Root.Elements("data")
                .FirstOrDefault(d => d.Attribute("name").Value.EqualsOrNullEmpty(element.Name, StringComparison.OrdinalIgnoreCase));
                if (newElement == null)
                {
                    return false;
                }
                newElement.Remove();
                document.Save(filePath);
                return true;
            }
            finally
            {
                locker.ExitWriteLock();
            }
        }
        private XDocument GetResxDocument(string filePath)
        {
            if (!File.Exists(filePath))
            {
                return null;
            }
            return XDocument.Load(filePath);
        }
        private XDocument CreateResXDocument()
        {
            string resheader = @"<root>
<xsd:schema id=""root"" xmlns="""" xmlns:xsd=""http://www.w3.org/2001/XMLSchema"" xmlns:msdata=""urn:schemas-microsoft-com:xml-msdata"">
<xsd:import namespace=""http://www.w3.org/XML/1998/namespace"" />
<xsd:element name=""root"" msdata:IsDataSet=""true"">
<xsd:complexType>
<xsd:choice maxOccurs=""unbounded"">
<xsd:element name=""metadata"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" />
</xsd:sequence>
<xsd:attribute name=""name"" use=""required"" type=""xsd:string"" />
<xsd:attribute name=""type"" type=""xsd:string"" />
<xsd:attribute name=""mimetype"" type=""xsd:string"" />
<xsd:attribute ref=""xml:space"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""assembly"">
<xsd:complexType>
<xsd:attribute name=""alias"" type=""xsd:string"" />
<xsd:attribute name=""name"" type=""xsd:string"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""data"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""1"" />
<xsd:element name=""comment"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""2"" />
</xsd:sequence>
<xsd:attribute name=""name"" type=""xsd:string"" use=""required"" msdata:Ordinal=""1"" />
<xsd:attribute name=""type"" type=""xsd:string"" msdata:Ordinal=""3"" />
<xsd:attribute name=""mimetype"" type=""xsd:string"" msdata:Ordinal=""4"" />
<xsd:attribute ref=""xml:space"" />
</xsd:complexType>
</xsd:element>
<xsd:element name=""resheader"">
<xsd:complexType>
<xsd:sequence>
<xsd:element name=""value"" type=""xsd:string"" minOccurs=""0"" msdata:Ordinal=""1"" />
</xsd:sequence>
<xsd:attribute name=""name"" type=""xsd:string"" use=""required"" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name=""resmimetype"">
<value>text/microsoft-resx</value>
</resheader>
<resheader name=""version"">
<value>2.0</value>
</resheader>
<resheader name=""reader"">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name=""writer"">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>";
            XElement root = XElement.Parse(resheader, LoadOptions.PreserveWhitespace);
            XDocument doc = new XDocument(root);
            return doc;
        }
    }
    public static class StringExtensions
    {
        public static string Ellipsis(this string str, int length, int ellipsisLength = 3)
        {
            if (str == null)
                return str;
            if (str.Length <= length)
                return str;
            return str.Substring(0, length - ellipsisLength).PadRight(length, '.');
        }
        public static bool EqualsOrNullEmpty(this string str1, string str2, StringComparison comparisonType)
        {
            return String.Compare(str1 ?? "", str2 ?? "", comparisonType) == 0;
        }
        public static string TrimOrNull(this string str)
        {
            if (str == null)
                return str;
            return str.Trim();
        }
    }
    public class Settings
    {
        public static string BaseDirectory
        {
            get
            {
                return AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
            }
        }
        public static string BinDirectory
        {
            get
            {
                if (IsWebApplication)
                {
                    return AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
                }
                else
                {
                    return AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
                }
            }
        }
        public static string ComponentsDirectory
        {
            get
            {
                return AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "Components";
            }
        }
        public static bool IsWebApplication
        {
            get
            {
                return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile.EndsWith("\\web.config");
            }
        }
    }
}